local log = require("mylog")
local config = require("config")
local t_permission = require("t_permission")
local dx_bit = require("dx_bit")
local dx_json = require("dx_json")
local dx_queue = require("dx_queue")
local gpio = require("dx_gpio")
local os_util = require("os_util")
local string_util = require("string_util")
local time = require("time")
require("update_config")

local serialport_service = {}

-- 是否是配置类数据
-- 普通
DATA_NORMAL = 1
-- 配置
DATA_CONFIG = 0

-- 通用方法执行成功/失败状态定义
local VBAR_ERROR = 1
local VBAR_NORMAL = 0
-- 通用SQL执行成功/失败状态定义
local SQL_ERROR = 1
local SQL_SUCCESS = 0

-- NFC配置卡固定前缀及长度
VBAR_HEAD_NFCCFG_LEN = 5

-- 读头上传的数据类型---二维码类型
SCANNER_QR_DATA = 0
-- 读头上传的数据类型---NFC类型
SCANNER_NFC_DATA = 1
-- 读头上传的数据类型---蓝牙类型
SCANNER_BLUETOOTH_DATA = 3

-- 信道上传数据前两字节校验数据、也用于尾部1字节校验
PRO_HEAD1 = "55"
PRO_HEAD2 = "AA"
PRO_CMD_04 = "04"
PRO_HEAD_NUM1 = 0x55
PRO_HEAD_NUM2 = 0xAA

-- 配置码固定前13位字符
VBAR_HEAD_DEVICECFG = "___VBAR_KZB_V"

-- 上次刷码时间
LAST_TIME = 0

-- 初始化读取配置信息！(所有require("update_config")的.lua文件都需要从线程中读取配置，每次修改都需要覆盖Map中的配置，且需要保证同一时间只能有一个线程操作该键值对)
while true do
    if dx_map.get(_G_map, "g_config_str") ~= nil and dx_map.get(_G_map, "vbar_config_t_str") ~= nil then
        local g_config_str = dx_map.get(_G_map, "g_config_str")
        local vbar_config_t_str = dx_map.get(_G_map, "vbar_config_t_str")
        g_config = dx_json.decode(g_config_str)
        vbar_config_t = dx_json.decode(vbar_config_t_str)
        break
    end
    time.sleep(0.01)
end
log.debug("[串口信道]打印从_G_map中读取的g_config表", dx_json.encode(g_config))

----------------------------------------------------------------------
-- 生成校验位
local function create_valid(pack)
    local cmd = dx_bit.xstr_to_dec_num(pack["cmd"], 16)

    local dlen = dx_bit.xstr_to_dec_num(pack["dlen"], 16)
    local crc = cmd

    crc = bit.bxor(crc, PRO_HEAD_NUM1)
    crc = bit.bxor(crc, (bit.band(bit.rshift(dlen, 0), 0xff)))

    crc = bit.bxor(crc, PRO_HEAD_NUM2)
    crc = bit.bxor(crc, (bit.band(bit.rshift(dlen, 8), 0xff)))

    local i = 1
    local data = pack["data"]
    while i <= #data do
        local j = i + 1
        local num = dx_bit.xstr_to_dec_num(string.sub(data, i, j), 16)
        crc = bit.bxor(crc, num)
        i = j + 1
    end
    return dx_bit.dec_num_to_xstr(crc, 16)
end

local function fill(str, fill_count)
    if #str < fill_count then
        while #str < fill_count do
            str = "0" .. str
        end
    end
    return str
end

local function channel_send_cmd_04(led, buzzer, count, con_time, interval_time)
    local pack = {}

    pack["head"] = PRO_HEAD1 .. PRO_HEAD2
    pack["cmd"] = PRO_CMD_04

    local type_ten = 0
    if led ~= 0 and led ~= nil then
        type_ten = type_ten + bit.lshift(1, led)
    end
    if buzzer ~= 0 and buzzer ~= nil then
        type_ten = type_ten + bit.lshift(1, buzzer)
    end

    local type = fill(dx_bit.dec_num_to_xstr(type_ten, 16), 2)

    con_time = fill(dx_bit.dec_num_to_xstr(con_time, 16), 2)

    interval_time = fill(dx_bit.dec_num_to_xstr(interval_time, 16), 2)
    count = fill(count .. "", 2)

    pack["data"] = type .. count .. con_time .. interval_time .. "00"
    pack["dlen"] = "0500"

    local crc = create_valid(pack)
    pack["crc"] = crc

    local req = pack["head"] .. pack["cmd"] .. pack["dlen"] .. pack["data"] .. pack["crc"]

    local len = #req / 2

    -- TODO发送数据
    -- channel.channel_send(vbar_channel, req, len)
end

--获取星期几
local function getWeek_comm(y, m, d)
    if m == 1 or m == 2 then
        m = m + 12
        y = y - 1
    end
    local m1, _ = math.modf(3 * (m + 1) / 5)
    local m2, _ = math.modf(y / 4)
    local m3, _ = math.modf(y / 100)
    local m4, _ = math.modf(y / 400)

    local iWeek = (d + 2 * m + m1 + y + m2 - m3 + m4) % 7
    local weekTab = {
        ["0"] = 1,
        ["1"] = 2,
        ["2"] = 3,
        ["3"] = 4,
        ["4"] = 5,
        ["5"] = 6,
        ["6"] = 7
    }
    return weekTab[tostring(iWeek)]
end

local function verify_check_and_save_record(identityCode, identityType)
    local res = t_permission.queryWhiteListByCode(identityCode, identityType)

    config.configStrToTable()
    if g_config["deviceStatus"] == "2" then
        --禁用状态校验
        -- 如果禁用，则直接失败
        log.info("通行失败，设备已被禁用")

        -- 给设备上送反馈指令 红灯蜂鸣一次，两秒
        channel_send_cmd_04(1, 3, 1, 10, 10)
        return SQL_ERROR
    elseif res ~= nil and res["code"] ~= nil and res["type"] ~= nil and res["id"] then
        local IdentityCode = res["code"]
        local IdentityType = res["type"]
        local permissionId = res["id"]
        local queue_msg =
            "20220316/event/access####" ..
            "IdentityCode@!@" ..
                IdentityCode .. "@#@IdentityType@!@" .. IdentityType .. "@#@permissionId@!@" .. permissionId
        -- 查询白名单
        log.info("查询到的结果：", dx_json.encode(res))
        -- 时间区域校验
        local time = dx_json.decode(res.time)
        if time.type == 0 then
            log.info("缺省模式，一直有效")
        else
            -- log.info("当前时间：" .. os.date("%Y-%m-%d %H:%M:%S", os.time()))
            -- log.info("当前时间戳：" .. os.time())
            local beginTime = time.beginTime
            local endTime = time.endTime
            if beginTime and endTime then
            else
                log.error("数据错误，通行失败")
                dx_queue.push(mqtt_queue, queue_msg .. "@#@result@!@" .. 0, 500)
                return SQL_ERROR
            end
            if time.type == 1 then
                -- log.info("通常模式，从开始到结束时间内有效")
                if os.time() >= beginTime and os.time() <= endTime then
                else
                    log.error("白名单过期，通行失败")
                    -- 给设备上送反馈指令 红灯蜂鸣一次，一秒
                    channel_send_cmd_04(1, 3, 1, 10, 10)
                    dx_queue.push(mqtt_queue, queue_msg .. "@#@result@!@" .. 0, 500)
                    return SQL_ERROR
                end
            else
                beginTime = string_util.split(beginTime, ":")
                endTime = string_util.split(endTime, ":")
                beginTime =
                    os.time(
                    {
                        day = tonumber(os.date("%d", os.time())),
                        month = tonumber(os.date("%m", os.time())),
                        year = tonumber(os.date("%Y", os.time())),
                        hour = tonumber(beginTime[1]),
                        min = tonumber(beginTime[2]),
                        sec = tonumber(beginTime[3])
                    }
                )
                endTime =
                    os.time(
                    {
                        day = tonumber(os.date("%d", os.time())),
                        month = tonumber(os.date("%m", os.time())),
                        year = tonumber(os.date("%Y", os.time())),
                        hour = tonumber(endTime[1]),
                        min = tonumber(endTime[2]),
                        sec = tonumber(endTime[3])
                    }
                )
                if time.type == 2 then
                    log.info("每日模式，每日特定时间到特定时间内有效")
                    if os.time() >= beginTime and os.time() <= endTime then
                    else
                        log.error("不在当日有效期内，通行失败")
                        -- 给设备上送反馈指令 红灯蜂鸣一次，一秒
                        channel_send_cmd_04(1, 3, 1, 10, 10)
                        dx_queue.push(mqtt_queue, queue_msg .. "@#@result@!@" .. 0, 500)
                        return SQL_ERROR
                    end
                elseif time.type == 3 then
                    log.info("工作日模式，周一到周五特定时间到特定时间内有效，其它时间无效")
                    local week =
                        getWeek_comm(
                        tonumber(os.date("%Y", os.time())),
                        tonumber(os.date("%m", os.time())),
                        tonumber(os.date("%d", os.time()))
                    )
                    if os.time() >= beginTime and os.time() <= endTime and week ~= 6 and week ~= 7 then
                    else
                        log.error("不在有效期内，通行失败")
                        -- 给设备上送反馈指令 红灯蜂鸣一次，一秒
                        channel_send_cmd_04(1, 3, 1, 10, 10)
                        dx_queue.push(mqtt_queue, queue_msg .. "@#@result@!@" .. 0, 500)
                        return SQL_ERROR
                    end
                end
            end
        end

        log.info("白名单：离线验证成功")
        -- 给设备上送反馈指令 绿灯蜂鸣一次，一秒
        channel_send_cmd_04(2, 3, 1, 5, 10)

        -- 远程开门
        log.info("执行开门")
        gpio.gpio_set_value(70, 1)
        os_util.sleep(g_config["openTime"])
        gpio.gpio_set_value(70, 0)

        -- 通行记录保存并上送
        dx_queue.push(mqtt_queue, queue_msg .. "@#@result@!@" .. 1, 500)

        return SQL_SUCCESS
    else
        local data = {}
        data.id = 68
        data.type = 404
        data.code = "1111111111111111"
        data.extra = "fff"
        data.time = 2022
        t_permission.dx_sqlite_save_whitelists(data)

        local res = t_permission.queryWhiteListByCode(data.code, data.type)
        log.info("查询到的code：", res.code)
        log.info("查询到的type：", res.type)
        log.info("白名单查询不到数据！不能通行！")
        -- 给设备上送反馈指令 红灯蜂鸣一次，一秒
        channel_send_cmd_04(1, 3, 1, 5, 10)
        local queue_msg =
            "20220316/event/access####" ..
            "IdentityCode@!@" .. identityCode .. "@#@IdentityType@!@" .. identityType .. "@#@permissionId@!@" .. "0"
        dx_queue.push(mqtt_queue, queue_msg .. "@#@result@!@" .. 0, 500)
        return SQL_ERROR
    end
end

-- 检查二维码类型：判断是配置码还是普通码(通行码)
local function check_scanner_qr_data(qr_data, has_valid)
    -- has_valid代表是否有1字节不可见校验位，TRUE代表有，FALSE代表没有
    local header_len = string.len(VBAR_HEAD_DEVICECFG)
    -- 这里为什么要从2到14呢？不应该是1到13吗？因为最前面有1为不可见字符
    local header
    if has_valid then
        header = string.sub(qr_data, 2, header_len + 1)
    else
        header = string.sub(qr_data, 1, header_len)
    end
    -- log.debug("header" .. header)
    if header == VBAR_HEAD_DEVICECFG then
        -- log.debug("配置码")
        return 0
    end
    -- log.debug("普通码")
    return 1
end

-- 解密二维码，读取传输数据
local function verify_analysis_transist_qrcode(data, datalen)
    local record = {}
    record["IdentityType"] = 101
    local b64_data = dx_bit.dec(data)
    if b64_data == nil then
        log.debug("[串口输入] base64解码失败")
        return nil
    end
    record["IdentityCode"] = string.sub(b64_data, 5, string.len(b64_data))
    log.debug("[串口输入] 解码后数据：", record["IdentityCode"])
    -- 把通行二维码，设备地址，等信息放到了结构体struct record中，根据该结构体校验是否能通过
    return verify_check_and_save_record(record["IdentityCode"], record["IdentityType"])
end

-- 去掉}--前面的字符串，即校验串 pos就是}--前面的字符串的长度
-- 对于字符串config，从后往前遍历，直到遍历到'--'停止。然后取得'--'前面的字符长度。例如："kzbabcdefg--abcd"，最后的结果就是16-6 = 10
local function get_separator_pos(config, configlen)
    local count = 0
    local strArray = string_util.str2strArray(config)
    for i = string.len(config), 1, -1 do
        count = count + 1
        if strArray[i] == "-" then
            i = i - 1
            count = count + 1
            if strArray[i] == "-" then
                break
            end
        end
    end
    return string.len(config) - count
end

-- 把配置码数据和旧的配置数据进行合并放到内存中, config + head_len，表示去除前面的___VBAR_KZB_V， pos-len表示中间数据
local function vbar_config_set_config_table(config_offset_str)
    log.debug("config_offset_str: " .. config_offset_str)
    -- 从Map中获取最新的配置vbar_config_t = g_config
    config.configStrToTable()
    -- merge(vbar_config_t,vbar_config_d)
    return updatecfg(config_offset_str)
end
-- 解码验证是否符合协议规范，并把数据进行保存
local function set_normal_config(config, configlen)
    -- 去掉}--前面的字符串，即校验串 pos就是}--前面的字符串的长度
    local pos = get_separator_pos(config, configlen)
    if pos <= 0 then
        log.debug("字符串不符合规定! ")
        return -1
    end
    local head_len = string.len(VBAR_HEAD_DEVICECFG)
    local b64_len = 0

    -- 这就是明文字符串了。例如：
    -- local config_offset_str = string.sub(config, 1, 13);
    local config_offset_str = string.sub(config, head_len + 1, pos)

    -- 把配置码数据和旧的配置数据进行合并放到内存中, config + head_len，表示去除前面的___VBAR_KZB_V， pos-len表示中间数据
    return vbar_config_set_config_table(config_offset_str)
end

-- 解码、掐头去尾、解析数据，最终保存数据到.txt文件中
local function parser_config_with_head(ddata, dlen)
    local ret = 0
    local device_id
    if ddata == nil or dlen < 1 then
        log.debug("数据不能为空！")
        return
    end

    -- 结果等于0表明是以“___VBAR_KZB_V”开头，是配置码
    if string_util.strncmp(ddata, VBAR_HEAD_DEVICECFG, #VBAR_HEAD_DEVICECFG) == 0 then
        -- 把配置码数据和旧的配置数据进行合并放到内存中
        ret = set_normal_config(ddata, dlen)
        if ret ~= 0 then
            log.debug("配置码有误，配置失败！")
            return 1
        end

        -- 判断ddata中是否包含"device_id"这样的键值对，如果有，解析出来，进行保存
        local dbegin = string.find(ddata, "{")
        local dend = string.find(ddata, "}")
        local configStr = string.sub(ddata, dbegin, dend)
        -- local configBody = string.gsub(configStr, "=", ":")
        log.debug("configBody: " .. configStr)
        local ok, configTable = serpent.load(configStr)
        if configTable == nil then
            log.debug("配置信息错误！")
            return
        end
        -- 判断是否为table，即二维码数据是我们指定的格式，是否能load为table
        if type(configTable) ~= "table" then
            log.debug("配置码格式错误！")
            return -1
        end
        log.debug("遍历is_config_t---------------")
        for k, v in pairs(configTable) do
            if type(v) == "boolean" then
                log.debug(k .. "---" .. tostring(v))
            else
                log.debug(k .. "---" .. v)
            end
            -- 配置信息中有device_id进行保存
            if k == "device_id" then
                -- TODO
                log.debug("保存device_id")
                -- TODO 需要感知是协议1还是协议2，协议1就是第一个串口，协议二就是第二个串口，我们这里先默认第一个
                local protocol = 1
                if protocol == 1 then
                    ret = updatecfg_single("device1_id", v)
                elseif protocol == 2 then
                    ret = updatecfg_single("device2_id", v)
                end
            end
        end
        log.debug("遍历is_config_t---------------")

        if ret < 0 then
            log.debug("保存device_id配置到vbar_config_t表失败！")
        end

        -- 保存解码得到的配置码的数据
        -- 上面的只是把信息保存到了lua的缓存table数据类型中，即保存到了vbar_config_t表中，这里才是真正的数据持久化
        log.debug("[parser_config_with_head]vbar_config_t: " .. serpent.line(vbar_config_t))
        -- 数据持久化，保存所有的配置信息(新+旧)
        ret = writecfg()
        if (ret < 0) then
            log.debug("配置数据持久化失败！")
            return -1
        end

        log.debug("符合协议规范，数据持久化成功!!!")
        return ret
    end
end

-- 拿到配置码中的数据，解析数据  此时的data就是纯数据了，首字节的校验位0x4?, 0x1?都被去除了
local function parser_config_data(ddata, dlen)
    if ddata == nil or dlen < 1 then
        log.debug("配置数据不能为空！")
    else
        -- 解析头header 拿到配置码中的数据，解析数据。这里其实就是解析header保存device1_id和device2_id到config配置文件中 真正的数据解析
        local ret = parser_config_with_head(ddata, dlen)
        if ret == nil or ret ~= VBAR_NORMAL then
            log.debug("配置数据、解析/保存失败!")
            -- 给设备上送反馈指令 红灯蜂鸣一次，一秒
            channel_send_cmd_04(1, 3, 2, 10, 10)
            return ret
        end
    end
    -- 给设备上送反馈指令 红灯蜂鸣一次，一秒
    channel_send_cmd_04(2, 3, 2, 10, 10)
    -- 设备重启！
    os_util.reboot(1)
    return VBAR_NORMAL
end

-- 解析配置码，读取数据
local function verify_config_data(data)
    -- 获取当前时间戳---毫秒级
    local nowTime = time.getNowTime()
    -- 接收信道数据时的时间
    local acceptTime = data["timestamp"]
    if math.abs(nowTime - acceptTime) > 1000 or math.abs(nowTime - LAST_TIME) < 1500 then
        log.debug("扫码到用码时间超过1000ms或两次配置间隔小于1500ms,直接销毁退出")
    end
    local ret = 0
    -- 拿到配置码中的数据，解析数据
    return parser_config_data(data["data"], data["dlen"])
end
----------------------------------------------------------------------

-- 二维码
function serialport_service.qr_code(pack)
    pack["type"] = SCANNER_QR_DATA
    -- 如果是普通码---则去除头部和尾部的固定子字符串
    if check_scanner_qr_data(pack["data"], true) ~= 0 then
        -- 这步是去除" &llgyAQAWADEyMzEyMzEyMzEyMzEyMzEyMzExMjM=@3Fa"前面的&llgy和后面的@3af等固定字符，为什么要从7开始呢？因为&前面还有1字节校验位，是不可见字符"
        pack["data"] = string.sub(pack["data"], 7, string.len(pack["data"]) - 4)
        log.debug("普通码数据: ", pack["data"])
    else
        log.debug("配置码数据: ", pack["data"])
        -- 去除首字节的校验位
        pack["data"] = string.sub(pack["data"], 2, string.len(pack["data"]))
        log.debug("配置码数据---去除首字节的校验位" .. pack["data"])
    end
    -- 判断是配置码还是普通码(通行码)，判断data.data中是否包含"___VBAR_KZB_V"，如果包含就表明二维码是配置码 xxia
    local ret = check_scanner_qr_data(pack["data"], false)
    if ret ~= 0 then
        -- 解析普通码
        verify_analysis_transist_qrcode(pack["data"], pack["dlen"])
    else
        -- 解析配置码
        verify_config_data(pack)
    end
end

-- 卡
function serialport_service.card(pack)
    pack["data"] = string.sub(pack["data"], 2, string.len(pack["data"]))
    pack["type"] = SCANNER_NFC_DATA

    -- 解析NFC刷卡数据
    local nfc_type = DATA_NORMAL
    if pack["dlen"] < VBAR_HEAD_NFCCFG_LEN then
        nfc_type = DATA_NORMAL
    end
    if VBAR_HEAD_NFCCFG == string.sub(pack["data"], 1, VBAR_HEAD_NFCCFG_LEN) then
        nfc_type = DATA_CONFIG
    end
    log.debug("卡类型(1---通行卡; 0---配置卡): ", nfc_type)
    if (nfc_type == DATA_CONFIG) then
        -- 解析NFC配置卡
    else
        -- 解析NFC普通卡(通行卡)
        log.debug("解析NFC通行卡！")

        local cardnumber = {}
        local mj_type = 202
        local id_len = pack["dlen"]
        -- 校验卡片类型，返回对应的type值和id值
        log.debug("typeHex: ", pack["typeHex"])
        log.debug("dlen: ", pack["dlen"])
        local mj_type, id_len = config.getTypeAndIdWithType(pack["typeHex"])

        if id_len > pack["dlen"] then
            id_len = pack["dlen"]
        end

        log.debug("data: ", pack["data"])
        log.debug("mj_type: ", mj_type)

        -- 根据type和code查询白名单，执行开门操作，保存通行记录并上送
        return verify_check_and_save_record(pack["data"], mj_type)
    end
end

-- 蓝牙
function serialport_service.bluetooth(pack)
    pack["type"] = SCANNER_BLUETOOTH_DATA
    pack["data"] = string.sub(pack["data"], 2, string.len(pack["data"]))
end

return serialport_service
